import sys
import ctypes

NULL = 0
DWORD = ctypes.c_ulong

class GUID(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ('Data1', ctypes.c_ulong),
        ('Data2', ctypes.c_ushort),
        ('Data3', ctypes.c_ushort),
        ('Data4', ctypes.c_ubyte*8),
    ]

class SP_DEVINFO_DATA(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ('cbSize', DWORD),
        ('ClassGuid', GUID),
        ('DevInst', DWORD),
        ('Reserved', ctypes.POINTER(ctypes.c_ulong)),
    ]

setupapi = ctypes.windll.setupapi
SetupDiDestroyDeviceInfoList = setupapi.SetupDiDestroyDeviceInfoList
SetupDiGetClassDevs = setupapi.SetupDiGetClassDevsW
SetupDiEnumDeviceInfo = setupapi.SetupDiEnumDeviceInfo
SetupDiGetDeviceRegistryProperty = setupapi.SetupDiGetDeviceRegistryPropertyW
SetupDiOpenDevRegKey = setupapi.SetupDiOpenDevRegKey
advapi32 = ctypes.windll.advapi32
RegQueryValueEx = advapi32.RegQueryValueExW
RegCloseKey = advapi32.RegCloseKey

GUID_CLASS_COMPORT = GUID(0x86e0d1e0L, 0x8089, 0x11d0,
    (ctypes.c_ubyte*8)(0x9c, 0xe4, 0x08, 0x00, 0x3e, 0x30, 0x1f, 0x73))

DIGCF_PRESENT = 2
DIGCF_DEVICEINTERFACE = 16
INVALID_HANDLE_VALUE = -1 
ERROR_INSUFFICIENT_BUFFER = 122
SPDRP_HARDWAREID = 1
SPDRP_FRIENDLYNAME = 12
SPDRP_LOCATION_INFORMATION = 13
SPDRP_PHYSICAL_DEVICE_OBJECT_NAME = 14
SPDRP_ENUMERATOR_NAME = 22
ERROR_NO_MORE_ITEMS = 259
NO_ERROR = 0
DICS_FLAG_GLOBAL = 1
DIREG_DEV = 0x00000001
DIREG_DRV = 0x00000002
DIREG_BOTH = 0x00000004
KEY_QUERY_VALUE = 0x00000001
KEY_ENUMERATE_SUB_KEYS = 0x00000008
KEY_READ = 0x00020019

def comPorts(available_only=True):
    portsList = []
    flags = DIGCF_DEVICEINTERFACE
    if available_only:
        flags |= DIGCF_PRESENT
    hDevInfo = SetupDiGetClassDevs(
            ctypes.byref(GUID_CLASS_COMPORT),
            None, 
            NULL, 
            flags)
    if hDevInfo == INVALID_HANDLE_VALUE:
        print GetLastError(), FormatError()
        exit(1)
    deviceInfoData = SP_DEVINFO_DATA()
    deviceInfoData.cbSize = ctypes.sizeof(SP_DEVINFO_DATA)
    for i in range(10000):
        if not SetupDiEnumDeviceInfo(hDevInfo, i, ctypes.byref(deviceInfoData)):
            break
        friendlyName = getDeviceProperty(hDevInfo, deviceInfoData, SPDRP_FRIENDLYNAME)
        physicalName =  getDeviceProperty(hDevInfo, deviceInfoData, SPDRP_PHYSICAL_DEVICE_OBJECT_NAME)
        enumeratorName = getDeviceProperty(hDevInfo, deviceInfoData, SPDRP_ENUMERATOR_NAME)
        hardwareId =  getDeviceProperty(hDevInfo, deviceInfoData, SPDRP_HARDWAREID)
        hkey = SetupDiOpenDevRegKey(hDevInfo, 
                ctypes.byref(deviceInfoData),
                DICS_FLAG_GLOBAL,
                0,
                DIREG_DEV,
                KEY_QUERY_VALUE)
        portName = getRegKeyValue(hkey, u"PortName")
        RegCloseKey(hkey)
        print u"{}\nFriendlyName: {}\nPhysicalName: {}\nEnumeratorName: {}\nHardwareId: {}\n".format(portName, friendlyName, physicalName, enumeratorName, hardwareId)
        portsList.append(portName)

    if ctypes.GetLastError() != NO_ERROR and ctypes.GetLastError() != ERROR_NO_MORE_ITEMS:
        print ctypes.FormatError()
        exit(1)
    SetupDiDestroyDeviceInfoList(hDevInfo)
    return portsList

def getRegKeyValue(hkey, property):
    size = DWORD()
    RegQueryValueEx(hkey, 
            ctypes.create_unicode_buffer(property),
            0,
            0,
            0,
            ctypes.byref(size))
    type = DWORD()
    buff = ctypes.create_unicode_buffer(size.value)
    RegQueryValueEx(hkey, 
            ctypes.create_unicode_buffer(property),
            0,
            ctypes.byref(type),
            ctypes.byref(buff),
            ctypes.byref(size))
    return buff.value

def getDeviceProperty(hDevInfo, deviceInfoData, property):
    DataT = DWORD()
    buff = ctypes.create_unicode_buffer(0)
    buff_lenth = DWORD(0)
    while not SetupDiGetDeviceRegistryProperty(
            hDevInfo,
            ctypes.byref(deviceInfoData),
            property,
            ctypes.byref(DataT),
            ctypes.byref(buff),
            buff_lenth,
            ctypes.byref(buff_lenth)):
        if ctypes.GetLastError() == ERROR_INSUFFICIENT_BUFFER:
            buff = ctypes.create_unicode_buffer(buff_lenth.value)
        else:
            print ctypes.FormatError()
            exit(1)
    return buff.value

if __name__ == '__main__':
    ports = comPorts()
    print '-'*78
    print ports
